﻿#region Copyright(c) 2006 ZO, All right reserved.
// -----------------------------------------------------------------------------
// Copyright(c) 2006 ZO, All right reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   1.  Redistribution of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//   2.  Redistribution in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//   3.  The name of the author may not be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// -----------------------------------------------------------------------------
#endregion


#region Using directives

using System;
using System.IO;
using System.Security.Cryptography;
using ZO.SmartCore.Core;
using ZO.SmartCore.Helpers;
using ZO.SmartCore.Security.Cryptography.Properties;
using ArgumentNullException= ZO.SmartCore.Core.ArgumentNullException;

#endregion

namespace ZO.SmartCore.Security.Cryptography
{
    /// <summary>
    ///     Represents basic cryptography services for a SymmetricAlgorithm. 
    /// </summary>
    /// <remarks>
    /// Because the IV (Initialization Vector) has the same distribution as the resulting 
    /// ciphertext, the IV is randomly generated and prepended to the ciphertext.
    /// </remarks>
    public sealed class SymmetricCryptographer : DisposableObject
    {

        #region Constructors
        /// <overloads>
        ///     Initializes a new instance of the <see cref="SymmetricCryptographer"/> class.
        /// </overloads>
        /// <summary>
        ///     Initializes a new instance of the <see cref="SymmetricCryptographer"/> class.
        /// </summary>
        public SymmetricCryptographer(byte[] key)
        {
            if (key == null) throw new ArgumentNullException("key");

            this._Key = key;
            this.symAlgorithm = GetAlgorithm(this.Algorithm);
        } // SymmetricCryptographer

  



        /// <summary>
        ///     Initializes a new instance of the <see cref="SymmetricCryptographer"/> class with algorithm type and secret key.
        /// </summary>
        /// <param name="algorithm">
        ///     <see cref="T:SmartCore.Security.Cryptography.SymmetricAlgorithm"/>
        /// 	<para>
        /// 	     algorithm type 
        /// 	 </para>
        /// </param>
        /// <param name="key">
        ///     <see cref="T:System.Array"/>
        /// 	<para>
        /// 	    secret key for the symmetric algorithm  
        /// 	 </para>
        /// </param>
        public SymmetricCryptographer(SymmetricAlgorithm algorithm, byte[] key)
        {
            if (key == null) throw new ArgumentNullException("key");
            this._Key = key;
            this._Algorithm = algorithm;
            this.symAlgorithm = GetAlgorithm(this.Algorithm);
        } // SymmetricCryptographer
        #endregion

        #region Destructor
    
        #endregion

        #region Fields
        /// <summary>
        ///     Symmetric Encrypter
        /// </summary>
        private System.Security.Cryptography.SymmetricAlgorithm symAlgorithm;

        /// <summary>
        ///     Symmetric Algorithm Type
        /// </summary>
        private SymmetricAlgorithm _Algorithm = SymmetricAlgorithm.AES;

        /// <summary>
        ///     Protected Key for the symmetric algorithm
        /// </summary>
        private byte[] _Key;


   
        #endregion

        #region Events

        #endregion

        #region Operators

        #endregion

        #region Properties

        ///// <summary>
        ///// Gets or sets the initialization vector.
        ///// </summary>
        ///// <value>The IV.</value>
        //private byte[] IV
        //{
        //    get { return _IV; }
        //    set { _IV = value; }
        //}
    

        /// <summary>
        ///     Gets or sets the secret key for the symmetric algorithm.
        /// </summary>
        /// 
        /// <value>
        ///     <see cref="T:System.Array"/>
        /// 	<para>
        /// 	      The secret key to be used for the symmetric algorithm.
        /// 	 </para>
        /// </value>
        public byte[] Key
        {
            get
            {
                return this._Key;
            } // get
            set
            {
                if (value == null) throw new ArgumentNullException("Key");
                this._Key = value;
            }

        } // Key


        /// <summary>
        ///     Get or Set the Symmetric Algorithm Type
        /// </summary>
        /// 
        /// <value>
        ///     <see cref="T:SmartCore.Security.Cryptography.SymmetricAlgorithm"/>
        /// 	<para>
        /// 	      
        /// 	 </para>
        /// </value>
        public SymmetricAlgorithm Algorithm
        {
            get { return _Algorithm; } // get
            set
            {
                if (this._Algorithm != value)
                {
                    _Algorithm = value;
                    this.symAlgorithm = GetAlgorithm(value);
                }
            } // set
        } // Algorithm
        #endregion

        #region Methods
        /// <summary>
        /// Override This Method To Dispose Unmanaged Resources.
        /// </summary>
        protected override void DisposeManagedResources()
        {
            if (symAlgorithm != null)
            {
                symAlgorithm.Clear();
                symAlgorithm = null;
            }
        }

        /// <summary>
        ///     Get Symmetric Alogoritm upon current type
        /// </summary>
        /// <param name="algorithmType">
        ///     <see cref="T:System.String"/>
        /// 	<para>
        /// 	      Symmetric Alogoritm Type
        /// 	 </para>
        /// </param>
        /// <returns>
        ///     <see cref="T:System.Security.Cryptography.SymmetricAlgorithm"/>
        /// 	<para>
        /// 	      SymmetricAlgorithm object
        /// 	 </para>
        /// </returns>
        private static System.Security.Cryptography.SymmetricAlgorithm GetAlgorithm(SymmetricAlgorithm algorithmType)
        {
            System.Security.Cryptography.SymmetricAlgorithm sa;
            try
            {
                Type type = Type.GetType(ReflectionHelper.GetDescription(algorithmType), true);
                sa = Activator.CreateInstance(type) as System.Security.Cryptography.SymmetricAlgorithm;
            } // try
            catch (Exception ex)
            {
                // We want to supress any type of exception here for security reasons.
                throw new CryptographicException(Resources.ExceptionCreatingAlgorithmInstance, ex);
            } // catch

            if (sa == null)
            {
                throw new CryptographicException(Resources.ExceptionCastingSymmetricAlgorithmInstance);
            } // if

            return sa;
        } // GetSymmetricAlgorithm


        private ICryptoTransform CreateEncrypter()
        {

            // We are using salt to make it harder to guess our key
            // using a dictionary attack.
            byte[] salt = new byte[] { (byte)this.Key.Length, 0, 1, 2, 3, 4, 5, 6, 7 };
            //byte[] salt = Encoding.ASCII.GetBytes(this.Key.Length.ToString());


            // The (Secret Key) will be generated from the specified
            // password and salt.

            Rfc2898DeriveBytes secretKey = new Rfc2898DeriveBytes(this.Key, salt, 1500);

            // Create a encryptor
            ICryptoTransform crypter = null;

            switch (this.Algorithm)
            {
                case SymmetricAlgorithm.DES:
                    crypter = this.symAlgorithm.CreateEncryptor(secretKey.GetBytes(7), secretKey.GetBytes(8));
                    break;

                case SymmetricAlgorithm.RC2:
                    crypter = this.symAlgorithm.CreateEncryptor(secretKey.GetBytes(16), secretKey.GetBytes(8));
                    break;

                // (the default Rijndael key length is 256 bit = 32 bytes) and
                // then 16 bytes for the IV (initialization vector),
                // (the default Rijndael IV length is 128 bit = 16 bytes)
                case SymmetricAlgorithm.Rijndael | SymmetricAlgorithm.AES:
                    crypter = this.symAlgorithm.CreateEncryptor(secretKey.GetBytes(32), secretKey.GetBytes(16));
                    break;

                case SymmetricAlgorithm.TripleDES:
                    crypter = this.symAlgorithm.CreateEncryptor(secretKey.GetBytes(21), secretKey.GetBytes(8));
                    break;
            }

            return crypter;
        }


        private ICryptoTransform CreateDecrypter()
        {

            // We are using salt to make it harder to guess our key
            // using a dictionary attack.
            // setup the password generator
            byte[] salt = new byte[] { (byte)this.Key.Length, 0, 1, 2, 3, 4, 5, 6, 7 };


            // The (Secret Key) will be generated from the specified
            // password and salt.

            Rfc2898DeriveBytes secretKey = new Rfc2898DeriveBytes(this.Key, salt, 1500);

            // Create a encryptor
            ICryptoTransform crypter = null;

            switch (this.Algorithm)
            {
                case SymmetricAlgorithm.DES:
                    crypter = this.symAlgorithm.CreateDecryptor(secretKey.GetBytes(7), secretKey.GetBytes(8));
                    break;

                case SymmetricAlgorithm.RC2:
                    crypter = this.symAlgorithm.CreateDecryptor(secretKey.GetBytes(16), secretKey.GetBytes(8));
                    break;

                // (the default Rijndael key length is 256 bit = 32 bytes) and
                // then 16 bytes for the IV (initialization vector),
                // (the default Rijndael IV length is 128 bit = 16 bytes)
                case SymmetricAlgorithm.Rijndael | SymmetricAlgorithm.AES:
                    crypter = this.symAlgorithm.CreateDecryptor(secretKey.GetBytes(32), secretKey.GetBytes(16));
                    break;

                case SymmetricAlgorithm.TripleDES:
                    crypter = this.symAlgorithm.CreateDecryptor(secretKey.GetBytes(21), secretKey.GetBytes(8));
                    break;
            }

            return crypter;
        }


        /// <summary>
        ///     Encrypts a secret using the configured Symmetric Algorithm
        /// </summary>
        /// <param name="plainText">
        ///     <see cref="T:System.Array"/>
        /// 	<para>
        /// 	      The input for which you want to encrypt.
        /// 	 </para>
        /// </param>
        /// <returns>
        ///     <see cref="T:System.Array"/>
        /// 	<para>
        /// 	     The resulting cipher text. 
        /// 	 </para>
        /// </returns>
        public byte[] Encrypt(byte[] plainText)
        {

            if (plainText == null)
            {
                throw new ArgumentNullException("plainText");
            }

            byte[] cipherBytes;

            //create a MemoryStream that is going to hold the encrypted bytes
            using (MemoryStream ms = new MemoryStream())
            {
                // Create a CryptoStream through which we are going to be processing our data.

                // CryptoStreamMode.Write means that we are going to be writing data

                // to the stream and the output will be written in the MemoryStream

                // we have provided. (always use write mode for encryption)

                using (CryptoStream cryptoStream = new CryptoStream(ms, CreateEncrypter(), CryptoStreamMode.Write))
                {




                    // Start the encryption process.

                    cryptoStream.Write(plainText, 0, plainText.Length);

                    // Finish encrypting.
                    cryptoStream.FlushFinalBlock();

                    // Convert our encrypted data from a memoryStream into a byte array.
                    cipherBytes = ms.ToArray();

                }
            }


            return cipherBytes;
        } // Encrypt

   


        /// <summary>
        ///     Decrypts a secret
        /// </summary>
        /// <param name="encryptedText">
        ///     <see cref="T:System.Array"/>
        /// 	<para>
        /// 	    text for which you want to decrypt.  
        /// 	 </para>
        /// </param>
        /// <returns>
        ///     <see cref="T:System.Array"/>
        /// 	<para>
        /// 	     The resulting plain text. 
        /// 	 </para>
        /// </returns>
        public byte[] Decrypt(byte[] encryptedText)
        {
            if (encryptedText == null)
            {
                throw new ArgumentNullException("encryptedText");
            }

            // Since at this point we don't know what the size of decrypted data
            // will be, allocate the buffer long enough to hold EncryptedData;
            // DecryptedData is never longer than EncryptedData.
            byte[] cipherBytes = new byte[encryptedText.Length];

            byte[] plainText;


            //create a MemoryStream that is going to hold the encrypted bytes
            using (MemoryStream ms = new MemoryStream(encryptedText))
            {
                // Create a CryptoStream through which we are going to be processing our data.
                // Create a CryptoStream. (always use Read mode for decryption).
                using (CryptoStream cryptoStream = new CryptoStream(ms, CreateDecrypter(), CryptoStreamMode.Read))
                {

                    // Start the decrypting process.
                    int decryptedCount = cryptoStream.Read(cipherBytes, 0, cipherBytes.Length);

                    plainText = new byte[decryptedCount];

                    Buffer.BlockCopy(cipherBytes, 0, plainText, 0, decryptedCount);
                }
            }


            return plainText;
        } // Decrypt

     
        #endregion
    }
}
